Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squiz!337
This commit is contained in:
Nastya 2024-06-04 01:13:56 +00:00
commit 40c029da34
32 changed files with 828 additions and 382 deletions

@ -40,6 +40,7 @@
"notistack": "^3.0.1", "notistack": "^3.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-colorful": "^5.6.1",
"react-cytoscapejs": "^2.0.0", "react-cytoscapejs": "^2.0.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",

@ -256,7 +256,7 @@ export const getIntegrationRules = async (
method: "GET", method: "GET",
url: `${API_URL}/rules/${quizID}`, url: `${API_URL}/rules/${quizID}`,
}); });
return [settingsResponse]; return [settingsResponse || null];
} catch (nativeError) { } catch (nativeError) {
const [error] = parseAxiosError(nativeError); const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить настройки интеграции. ${error}`]; return [null, `Не удалось получить настройки интеграции. ${error}`];
@ -331,3 +331,18 @@ export const getCustomFields = async (
return [null, `Не удалось получить список кастомных полей. ${error}`]; return [null, `Не удалось получить список кастомных полей. ${error}`];
} }
}; };
//Отвязать аккаунт амо от публикации
export const removeAmoAccount = async (): Promise<[void | null, string?]> => {
try {
await makeRequest<void>({
method: "DELETE",
url: `${API_URL}/delete`,
});
return [null, ""];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отвязать аккаунт. ${error}`];
}
};

@ -96,6 +96,10 @@ export type AnyTypedQuizQuestion =
| QuizQuestionRating | QuizQuestionRating
| QuizQuestionResult; | QuizQuestionResult;
export type AllTypesQuestion =
| AnyTypedQuizQuestion
| UntypedQuizQuestion;
type FilterQuestionsWithVariants<T> = T extends { type FilterQuestionsWithVariants<T> = T extends {
content: { variants: QuestionVariant[] }; content: { variants: QuestionVariant[] };
} }

@ -1,4 +1,4 @@
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared"; import type { PreviewRule, QuestionHint, QuizQuestionBase } from "./shared";
export interface QuizQuestionText extends QuizQuestionBase { export interface QuizQuestionText extends QuizQuestionBase {
type: "text"; type: "text";
@ -13,7 +13,7 @@ export interface QuizQuestionText extends QuizQuestionBase {
required: boolean; required: boolean;
/** Чекбокс "Автозаполнение адреса" */ /** Чекбокс "Автозаполнение адреса" */
autofill: boolean; autofill: boolean;
answerType: "single" | "multi"; answerType: "single" | "multi" | "numberOnly";
hint: QuestionHint; hint: QuestionHint;
rule: PreviewRule; rule: PreviewRule;
back: string; back: string;

@ -37,21 +37,17 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const [widgetWidth, setWidgetWidth] = useState<string>(""); const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>(""); const [widgetHeight, setWidgetHeight] = useState<string>("");
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] = const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] = useState<boolean>(false);
useState<boolean>(false);
const [hideOnMobile, setHideOnMobile] = useState<boolean>(false); const [hideOnMobile, setHideOnMobile] = useState<boolean>(false);
const [rounded, setRounded] = useState<boolean>(false); const [rounded, setRounded] = useState<boolean>(false);
const [withShadow, setWithShadow] = useState<boolean>(false); const [withShadow, setWithShadow] = useState<boolean>(false);
const [buttonFlash, setButtonFlash] = useState<boolean>(false); const [buttonFlash, setButtonFlash] = useState<boolean>(false);
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>( const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(theme.palette.brightPurple.main);
theme.palette.brightPurple.main,
);
const [buttonTextColor, setButtonTextColor] = useState<string>("#FFFFFF"); const [buttonTextColor, setButtonTextColor] = useState<string>("#FFFFFF");
const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false); const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false);
const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10); const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10);
const [openOnLeaveAttempt, setOpenOnLeaveAttempt] = useState<boolean>(false); const [openOnLeaveAttempt, setOpenOnLeaveAttempt] = useState<boolean>(false);
const [position, setPosition] = const [position, setPosition] = useState<BannerWidgetParams["position"]>("bottomleft");
useState<BannerWidgetParams["position"]>("bottomleft");
const [bannerFullWidth, setBannerFullWidth] = useState<boolean>(false); const [bannerFullWidth, setBannerFullWidth] = useState<boolean>(false);
const [autoShowWidgetTime, setAutoShowWidgetTime] = useState<number>(10); const [autoShowWidgetTime, setAutoShowWidgetTime] = useState<number>(10);
const [appealText, setAppealText] = useState<string>(""); const [appealText, setAppealText] = useState<string>("");
@ -88,12 +84,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
: undefined, : undefined,
}); });
return ( return <WidgetScript scriptText={scriptText} helperText="Установите код внутрь тэга head" />;
<WidgetScript
scriptText={scriptText}
helperText="Установите код внутрь тэга head"
/>
);
} }
return ( return (
@ -129,17 +120,39 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
sx={[ sx={[
{ {
position: "absolute", position: "absolute",
top: "calc(12.5 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
width: "calc(196 / 520 * 100%)", width: "calc(196 / 520 * 100%)",
height: "calc(30 / 262 * 100%)", height: "calc(30 / 262 * 100%)",
}, },
bannerFullWidth && { position === "topleft" && {
top: "calc(7 / 262 * 100%)", top: "calc(12.5 / 262 * 100%)",
left: "calc(45 / 520 * 100%)", left: "calc(50.6 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
}, },
position === "topright" && {
top: "calc(12.5 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
position === "bottomleft" && {
top: "calc(178 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
},
position === "bottomright" && {
top: "calc(178 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
bannerFullWidth &&
position.startsWith("top") && {
top: "calc(7 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
bannerFullWidth &&
position.startsWith("bottom") && {
top: "calc(185 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
pulsation && { pulsation && {
":before": { ":before": {
content: "''", content: "''",
@ -196,16 +209,10 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
color: "white", color: "white",
}} }}
> >
<Typography <Typography fontSize="calc(6 / 29.5 * 100cqh)" lineHeight="120%">
fontSize="calc(6 / 29.5 * 100cqh)"
lineHeight="120%"
>
{appealText || "Пройти тест"} {appealText || "Пройти тест"}
</Typography> </Typography>
<Typography <Typography fontSize="calc(11 / 29.5 * 100cqh)" lineHeight="120%">
fontSize="calc(11 / 29.5 * 100cqh)"
lineHeight="120%"
>
{quizHeaderText || "Заголовок теста"} {quizHeaderText || "Заголовок теста"}
</Typography> </Typography>
</Box> </Box>
@ -220,8 +227,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
height: "calc(10 / 29.5 * 100%)", height: "calc(10 / 29.5 * 100%)",
width: "auto", width: "auto",
aspectRatio: 1, aspectRatio: 1,
backgroundColor: backgroundColor: rounded || bannerFullWidth ? "#581CA7" : undefined,
rounded || bannerFullWidth ? "#581CA7" : undefined,
borderRadius: "50%", borderRadius: "50%",
}} }}
/> />
@ -255,9 +261,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
gap: "20px", gap: "20px",
}} }}
> >
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Ширина окна (px)</Typography>
Ширина окна (px)
</Typography>
<PenaTextField <PenaTextField
type="number" type="number"
value={widgetWidth} value={widgetWidth}
@ -273,9 +277,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
gap: "20px", gap: "20px",
}} }}
> >
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Высота окна (px)</Typography>
Высота окна (px)
</Typography>
<PenaTextField <PenaTextField
type="number" type="number"
value={widgetHeight} value={widgetHeight}
@ -286,18 +288,14 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
</Box> </Box>
</Box> </Box>
</Collapse> </Collapse>
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Текст-призыв</Typography>
Текст-призыв
</Typography>
<PenaTextField <PenaTextField
placeholder="Пройти тест" placeholder="Пройти тест"
value={appealText} value={appealText}
onChange={(e) => setAppealText(e.target.value)} onChange={(e) => setAppealText(e.target.value)}
FormControlSx={{ maxWidth: "360px" }} FormControlSx={{ maxWidth: "360px" }}
/> />
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Заголовок quiz</Typography>
Заголовок quiz
</Typography>
<PenaTextField <PenaTextField
placeholder="Заголовок quiz" placeholder="Заголовок quiz"
value={quizHeaderText} value={quizHeaderText}
@ -311,9 +309,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
alignItems: "center", alignItems: "center",
}} }}
> >
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
Показывать через
</Typography>
<PenaTextField <PenaTextField
type="number" type="number"
value={autoShowWidgetTime} value={autoShowWidgetTime}
@ -322,9 +318,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
width: "90px", width: "90px",
}} }}
/> />
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
секунд
</Typography>
</Box> </Box>
<Box <Box
sx={{ sx={{
@ -333,20 +327,10 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
alignItems: "center", alignItems: "center",
}} }}
> >
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Цвет кнопки</Typography>
Цвет кнопки <CircleColorPicker color={buttonBackgroundColor} onChange={(color) => setButtonBackgroundColor(color)} />
</Typography> <Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста кнопки</Typography>
<CircleColorPicker <CircleColorPicker color={buttonTextColor} onChange={(color) => setButtonTextColor(color)} />
color={buttonBackgroundColor}
onChange={(color) => setButtonBackgroundColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет текста кнопки
</Typography>
<CircleColorPicker
color={buttonTextColor}
onChange={(color) => setButtonTextColor(color)}
/>
</Box> </Box>
<CustomCheckbox <CustomCheckbox
label="Баннер на всю ширину экрана" label="Баннер на всю ширину экрана"
@ -356,9 +340,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
if (e.target.checked) setPosition("topleft"); if (e.target.checked) setPosition("topleft");
}} }}
/> />
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Расположение</Typography>
Расположение
</Typography>
<FormControl> <FormControl>
<RadioGroup <RadioGroup
value={position} value={position}
@ -384,17 +366,13 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
<FormControlLabel <FormControlLabel
label="Сверху страницы" label="Сверху страницы"
value="topleft" value="topleft"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
<FormControlLabel <FormControlLabel
label="Снизу страницы" label="Снизу страницы"
value="bottomleft" value="bottomleft"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
</Box> </Box>
@ -409,23 +387,13 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
<FormControlLabel <FormControlLabel
label="Слева сверху" label="Слева сверху"
value="topleft" value="topleft"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
<FormControlLabel <FormControlLabel
label="Справа сверху" label="Слева снизу"
value="topright" value="bottomleft"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
</Box> </Box>
@ -436,25 +404,15 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
}} }}
> >
<FormControlLabel <FormControlLabel
label="Слева снизу" label="Справа сверху"
value="bottomleft" value="topright"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
<FormControlLabel <FormControlLabel
label="Справа снизу" label="Справа снизу"
value="bottomright" value="bottomright"
control={ control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }} sx={{ color: theme.palette.grey2.main }}
/> />
</Box> </Box>
@ -462,26 +420,12 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
)} )}
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Параметры</Typography>
Параметры
</Typography>
{!bannerFullWidth && ( {!bannerFullWidth && (
<CustomCheckbox <CustomCheckbox label="Закругленная" checked={rounded} handleChange={(e) => setRounded(e.target.checked)} />
label="Закругленная"
checked={rounded}
handleChange={(e) => setRounded(e.target.checked)}
/>
)} )}
<CustomCheckbox <CustomCheckbox label="С тенью" checked={withShadow} handleChange={(e) => setWithShadow(e.target.checked)} />
label="С тенью" <CustomCheckbox label="С бликом" checked={buttonFlash} handleChange={(e) => setButtonFlash(e.target.checked)} />
checked={withShadow}
handleChange={(e) => setWithShadow(e.target.checked)}
/>
<CustomCheckbox
label="С бликом"
checked={buttonFlash}
handleChange={(e) => setButtonFlash(e.target.checked)}
/>
<CustomCheckbox <CustomCheckbox
label={'Эффект "пульсация"'} label={'Эффект "пульсация"'}
checked={pulsation} checked={pulsation}
@ -546,22 +490,16 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
alignItems: "center", alignItems: "center",
}} }}
> >
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
Показывать через
</Typography>
<PenaTextField <PenaTextField
type="number" type="number"
value={autoShowQuizTime} value={autoShowQuizTime}
onChange={(e) => onChange={(e) => setAutoShowQuizTime(parseInt(e.target.value))}
setAutoShowQuizTime(parseInt(e.target.value))
}
FormControlSx={{ FormControlSx={{
width: "90px", width: "90px",
}} }}
/> />
<Typography sx={{ color: theme.palette.grey2.main }}> <Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
секунд
</Typography>
</Box> </Box>
</Box> </Box>
</Collapse> </Collapse>

@ -8,24 +8,29 @@ import {
import React, { FC, useEffect, useMemo, useState } from "react"; import React, { FC, useEffect, useMemo, useState } from "react";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { AmoRemoveAccount } from "./AmoRemoveAccount/AmoRemoveAccount";
import { AmoLogin } from "./AmoLogin/AmoLogin"; import { AmoLogin } from "./AmoLogin/AmoLogin";
import { AmoStep2 } from "./AmoStep2/AmoStep2"; import { AmoStep2 } from "./AmoStep2/AmoStep2";
import { AmoStep3 } from "./AmoStep3/AmoStep3"; import { AmoStep3 } from "./AmoStep3/AmoStep3";
import { AmoStep4 } from "./AmoStep4/AmoStep4"; import { AmoStep4 } from "./AmoStep4/AmoStep4";
import { AmoStep6 } from "./IntegrationStep6/AmoStep6"; import { AmoStep6 } from "./IntegrationStep6/AmoStep6";
import { AmoStep7 } from "./IntegrationStep7/AmoStep7";
import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle"; import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle";
import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock"; import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock";
import { AmoStep7 } from "./IntegrationStep7/AmoStep7";
import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo"; import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo";
import { AccountResponse, getAccount } from "@api/integration"; import { AccountResponse, IntegrationRules, getAccount, getIntegrationRules } from "@api/integration";
import { useQuestions } from "@/stores/questions/hooks";
import { redirect } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
export type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers"; export type TitleKeys = "contacts" | "company" | "deal" | "buyers";
export type TQuestionEntity = Record<TitleKeys, string[] | []>; export type TQuestionEntity = Record<TitleKeys, string[] | []>;
type IntegrationsModalProps = { type IntegrationsModalProps = {
isModalOpen: boolean; isModalOpen: boolean;
handleCloseModal: () => void; handleCloseModal: () => void;
companyName: string | null; companyName: string | null;
quizID: number | undefined;
}; };
export type TagKeys = "contact" | "company" | "deal" | "buyer"; export type TagKeys = "contact" | "company" | "deal" | "buyer";
@ -35,15 +40,20 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
isModalOpen, isModalOpen,
handleCloseModal, handleCloseModal,
companyName, companyName,
quizID,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const { questions } = useQuestions();
const [step, setStep] = useState<number>(0); const [step, setStep] = useState<number>(0);
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false); const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
const [isRemoveAccount, setIsRemoveAccount] = useState<boolean>(false);
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null); const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
const [integrationRules, setIntegrationRules] = useState<IntegrationRules | null>(null);
const [selectedPipelinePerformer, setSelectedPipelinePerformer] = useState< const [selectedPipelinePerformer, setSelectedPipelinePerformer] = useState<
string | null string | null
>(null); >(null);
@ -56,10 +66,9 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
string | null string | null
>(null); >(null);
const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({ const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({
deal: [],
contacts: [], contacts: [],
company: [], company: [],
deal: [],
users: [],
buyers: [], buyers: [],
}); });
const [tags, setTags] = useState<TTags>({ const [tags, setTags] = useState<TTags>({
@ -68,21 +77,38 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
company: [], company: [],
buyer: [], buyer: [],
}); });
console.log(accountInfo) console.log(questionEntity)
console.log(tags)
useEffect(() => { useEffect(() => {
if (isModalOpen) { if (isModalOpen && quizID !== undefined && !isRemoveAccount) {
const fetchAccount = async () => { const fetchAccount = async () => {
const [account, error] = await getAccount(); const [account, error] = await getAccount();
if (account && !error) {
setAccountInfo(account); if (error) {
} else { enqueueSnackbar(error)
setAccountInfo(null); setAccountInfo(null);
} }
if (account) {
setAccountInfo(account);
}
}; };
const fetchRules = async () => {
const [settingsResponse, error] = await getIntegrationRules(quizID.toString());
if (error) {
enqueueSnackbar(error)
setIntegrationRules(null);
}
if (settingsResponse) {
setIntegrationRules(settingsResponse);
}
};
fetchAccount(); fetchAccount();
fetchRules();
} }
}, [isModalOpen]); }, [isModalOpen, isRemoveAccount]);
const handleNextStep = () => { const handleNextStep = () => {
setStep((prevState) => prevState + 1); setStep((prevState) => prevState + 1);
@ -152,26 +178,27 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
), ),
}, },
{ {
title: "Соотнесение вопросов и сущностей", title: "Добавление тегов",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<AmoStep6 <AmoStep6
questionEntity={questionEntity} tags={tags}
setQuestionEntity={setQuestionEntity}
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep} handleNextStep={handleNextStep}
setTags={setTags}
/> />
), ),
}, },
{ {
title: "Добавление тегов", title: "Соотнесение вопросов и сущностей",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<AmoStep7 <AmoStep7
handleSmallBtn={handlePrevStep} questionEntity={questionEntity}
handleLargeBtn={handleSave} setQuestionEntity={setQuestionEntity}
tags={tags} handlePrevStep={handlePrevStep}
setTags={setTags} handleNextStep={handleSave}
questions={questions}
/> />
), ),
}, },
@ -190,6 +217,9 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
const stepTitles = steps.map((step) => step.title); const stepTitles = steps.map((step) => step.title);
//Если нет контекста квиза, то и делать на этой страничке нечего
if (quizID === undefined) redirect("/list")
return ( return (
<Dialog <Dialog
open={isModalOpen} open={isModalOpen}
@ -252,8 +282,14 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
isSettingsBlock={isSettingsBlock} isSettingsBlock={isSettingsBlock}
setIsSettingsBlock={setIsSettingsBlock} setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep} setStep={setStep}
startRemoveAccount={() => setIsRemoveAccount(true)}
/> />
{isSettingsBlock ? ( {isRemoveAccount && (
<AmoRemoveAccount
stopThisPage={() => setIsRemoveAccount(false)}
/>
)}
{isSettingsBlock && (
<Box sx={{ flexGrow: 1, width: "100%" }}> <Box sx={{ flexGrow: 1, width: "100%" }}>
<AmoSettingsBlock <AmoSettingsBlock
stepTitles={stepTitles} stepTitles={stepTitles}
@ -268,7 +304,8 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
tags={tags} tags={tags}
/> />
</Box> </Box>
) : ( )}
{!isSettingsBlock && !isRemoveAccount && (
<Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box> <Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box>
)} )}
</Box> </Box>

@ -10,6 +10,7 @@ type AmoModalTitleProps = {
isSettingsBlock?: boolean; isSettingsBlock?: boolean;
setIsSettingsBlock: (value: boolean) => void; setIsSettingsBlock: (value: boolean) => void;
setStep: (value: number) => void; setStep: (value: number) => void;
startRemoveAccount: () => void;
}; };
export const AmoModalTitle: FC<AmoModalTitleProps> = ({ export const AmoModalTitle: FC<AmoModalTitleProps> = ({
@ -18,13 +19,16 @@ export const AmoModalTitle: FC<AmoModalTitleProps> = ({
setIsSettingsBlock, setIsSettingsBlock,
isSettingsBlock, isSettingsBlock,
setStep, setStep,
startRemoveAccount
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const handleClick = useCallback(() => { console.log(isSettingsBlock)
const handleClick = useCallback(async () => {
if (isSettingsBlock) { if (isSettingsBlock) {
setIsSettingsBlock(false); startRemoveAccount();
setIsSettingsBlock(false)
setStep(0); setStep(0);
return; return;
} }

@ -0,0 +1,61 @@
import { FC } from "react"
import { Button, Typography, useTheme, Box } from "@mui/material"
import { removeAmoAccount } from "@/api/integration";
import { enqueueSnackbar } from "notistack";
interface Props {
stopThisPage: () => void;
}
export const AmoRemoveAccount: FC<Props> = ({
stopThisPage,
}: Props) => {
const theme = useTheme();
const removeAccount = async () => {
const [, error] = await removeAmoAccount()
if (error) {
enqueueSnackbar(error)
} else {
stopThisPage()
}
};
return (
<Box
sx={{
mt: "30px"
}}
>
<Typography textAlign="center">
Вы хотите сменить аккаунт?
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "space-evenly",
flexWrap: "wrap",
margin: "30px auto",
}}
>
<Button
variant="contained"
sx={{
width: "150px",
}}
onClick={stopThisPage}
>отмена</Button>
<Button
variant="contained"
sx={{
width: "150px",
}}
onClick={removeAccount}
>сменить</Button>
</Box>
</Box >
)
}

@ -7,23 +7,24 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
import { TitleKeys, TQuestionEntity } from "../AmoCRMModal";
import Box from "@mui/material/Box";
type AmoStep6Props = { import { TagKeys, TTags } from "../AmoCRMModal";
handlePrevStep: () => void; import Box from "@mui/material/Box";
import { ItemsSelectionView } from "../IntegrationStep7/ItemsSelectionView/ItemsSelectionView";
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
type Props = {
handleNextStep: () => void; handleNextStep: () => void;
questionEntity: TQuestionEntity; handlePrevStep: () => void;
setQuestionEntity: Dispatch<SetStateAction<TQuestionEntity>>; tags: TTags;
setTags: Dispatch<SetStateAction<TTags>>;
}; };
export const AmoStep6: FC<AmoStep6Props> = ({ export const AmoStep6: FC<Props> = ({
handlePrevStep,
handleNextStep, handleNextStep,
questionEntity, handlePrevStep,
setQuestionEntity, tags,
setTags,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const [isSelection, setIsSelection] = useState<boolean>(false); const [isSelection, setIsSelection] = useState<boolean>(false);
@ -33,14 +34,14 @@ export const AmoStep6: FC<AmoStep6Props> = ({
const handleAdd = useCallback(() => { const handleAdd = useCallback(() => {
if (!activeItem || !selectedValue) return; if (!activeItem || !selectedValue) return;
setQuestionEntity((prevState) => ({ setTags((prevState) => ({
...prevState, ...prevState,
[activeItem]: [...prevState[activeItem as TitleKeys], selectedValue], [activeItem]: [...prevState[activeItem as TagKeys], selectedValue],
})); }));
}, [activeItem, setQuestionEntity, selectedValue]); }, [activeItem, setTags, selectedValue]);
const items = useMemo( const items = useMemo(
() => ["Город", "Имя", "Фамилия", "Отчество", "Контрагент"], () => ["#тег с результатом 1", "#еще один тег с результатом 2", "#тег"],
[], [],
); );
@ -58,6 +59,7 @@ export const AmoStep6: FC<AmoStep6Props> = ({
items={items} items={items}
selectedValue={selectedValue} selectedValue={selectedValue}
setSelectedValue={setSelectedValue} setSelectedValue={setSelectedValue}
type={"typeTags"}
onSmallBtnClick={() => { onSmallBtnClick={() => {
setActiveItem(null); setActiveItem(null);
setIsSelection(false); setIsSelection(false);
@ -69,11 +71,11 @@ export const AmoStep6: FC<AmoStep6Props> = ({
}} }}
/> />
) : ( ) : (
<ItemDetailsView <TagsDetailsView
setIsSelection={setIsSelection} setIsSelection={setIsSelection}
handleNextStep={handleNextStep}
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
questionEntity={questionEntity} handleNextStep={handleNextStep}
tags={tags}
setActiveItem={setActiveItem} setActiveItem={setActiveItem}
/> />
)} )}

@ -2,19 +2,19 @@ import { Box, Typography, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
import { TagKeys, TTags } from "../../AmoCRMModal"; import { TagKeys, TTags } from "../../AmoCRMModal";
import { Item } from "../../IntegrationStep6/Item/Item"; import { Item } from "../../IntegrationStep7/Item/Item";
type TagsDetailsViewProps = { type TagsDetailsViewProps = {
setIsSelection: (value: boolean) => void; setIsSelection: (value: boolean) => void;
handleSmallBtn: () => void; handlePrevStep: () => void;
handleLargeBtn: () => void; handleNextStep: () => void;
tags: TTags; tags: TTags;
setActiveItem: (value: string | null) => void; setActiveItem: (value: string | null) => void;
}; };
export const TagsDetailsView: FC<TagsDetailsViewProps> = ({ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
handleSmallBtn, handlePrevStep,
handleLargeBtn, handleNextStep,
tags, tags,
setActiveItem, setActiveItem,
setIsSelection, setIsSelection,
@ -89,9 +89,8 @@ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
}} }}
> >
<StepButtonsBlock <StepButtonsBlock
onSmallBtnClick={handleSmallBtn} onSmallBtnClick={handlePrevStep}
onLargeBtnClick={handleLargeBtn} onLargeBtnClick={handleNextStep}
largeBtnText={"Сохранить"}
/> />
</Box> </Box>
</Box> </Box>

@ -7,24 +7,26 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
import { TagKeys, TTags } from "../AmoCRMModal"; import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
import { TitleKeys, TQuestionEntity } from "../AmoCRMModal";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { ItemsSelectionView } from "../IntegrationStep6/ItemsSelectionView/ItemsSelectionView"; import type { AllTypesQuestion } from "@model/questionTypes/shared"
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
type AmoStep7Props = { type Props = {
handleSmallBtn: () => void; handlePrevStep: () => void;
handleLargeBtn: () => void; handleNextStep: () => void;
tags: TTags; questionEntity: TQuestionEntity;
setTags: Dispatch<SetStateAction<TTags>>; setQuestionEntity: Dispatch<SetStateAction<TQuestionEntity>>;
questions: AllTypesQuestion[];
}; };
export const AmoStep7: FC<AmoStep7Props> = ({ export const AmoStep7: FC<Props> = ({
handleSmallBtn, handlePrevStep,
handleLargeBtn, handleNextStep,
tags, questionEntity,
setTags, setQuestionEntity,
questions,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const [isSelection, setIsSelection] = useState<boolean>(false); const [isSelection, setIsSelection] = useState<boolean>(false);
@ -34,14 +36,14 @@ export const AmoStep7: FC<AmoStep7Props> = ({
const handleAdd = useCallback(() => { const handleAdd = useCallback(() => {
if (!activeItem || !selectedValue) return; if (!activeItem || !selectedValue) return;
setTags((prevState) => ({ setQuestionEntity((prevState) => ({
...prevState, ...prevState,
[activeItem]: [...prevState[activeItem as TagKeys], selectedValue], [activeItem]: [...prevState[activeItem as TitleKeys], selectedValue],
})); }));
}, [activeItem, setTags, selectedValue]); }, [activeItem, setQuestionEntity, selectedValue]);
const items = useMemo( const items = useMemo(
() => ["#тег с результатом 1", "#еще один тег с результатом 2", "#тег"], () => ["Город", "Имя", "Фамилия", "Отчество", "Контрагент"],
[], [],
); );
@ -59,7 +61,6 @@ export const AmoStep7: FC<AmoStep7Props> = ({
items={items} items={items}
selectedValue={selectedValue} selectedValue={selectedValue}
setSelectedValue={setSelectedValue} setSelectedValue={setSelectedValue}
type={"typeTags"}
onSmallBtnClick={() => { onSmallBtnClick={() => {
setActiveItem(null); setActiveItem(null);
setIsSelection(false); setIsSelection(false);
@ -69,13 +70,14 @@ export const AmoStep7: FC<AmoStep7Props> = ({
setActiveItem(null); setActiveItem(null);
setIsSelection(false); setIsSelection(false);
}} }}
questions={questions}
/> />
) : ( ) : (
<TagsDetailsView <ItemDetailsView
setIsSelection={setIsSelection} setIsSelection={setIsSelection}
handleLargeBtn={handleLargeBtn} handleLargeBtn={handleNextStep}
handleSmallBtn={handleSmallBtn} handleSmallBtn={handlePrevStep}
tags={tags} questionEntity={questionEntity}
setActiveItem={setActiveItem} setActiveItem={setActiveItem}
/> />
)} )}

@ -4,19 +4,19 @@ import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
import { TQuestionEntity } from "../../AmoCRMModal"; import { TQuestionEntity } from "../../AmoCRMModal";
type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers"; type TitleKeys = "contacts" | "company" | "deal" | "buyers";
type ItemDetailsViewProps = { type ItemDetailsViewProps = {
setIsSelection: (value: boolean) => void; setIsSelection: (value: boolean) => void;
handlePrevStep: () => void; handleSmallBtn: () => void;
handleNextStep: () => void; handleLargeBtn: () => void;
questionEntity: TQuestionEntity; questionEntity: TQuestionEntity;
setActiveItem: (value: string | null) => void; setActiveItem: (value: string | null) => void;
}; };
export const ItemDetailsView: FC<ItemDetailsViewProps> = ({ export const ItemDetailsView: FC<ItemDetailsViewProps> = ({
handlePrevStep, handleSmallBtn,
handleNextStep, handleLargeBtn,
questionEntity, questionEntity,
setActiveItem, setActiveItem,
setIsSelection, setIsSelection,
@ -69,8 +69,9 @@ export const ItemDetailsView: FC<ItemDetailsViewProps> = ({
}} }}
> >
<StepButtonsBlock <StepButtonsBlock
onSmallBtnClick={handlePrevStep} onSmallBtnClick={handleSmallBtn}
onLargeBtnClick={handleNextStep} onLargeBtnClick={handleLargeBtn}
largeBtnText={"Сохранить"}
/> />
</Box> </Box>
</Box> </Box>

@ -2,6 +2,7 @@ import { Box } from "@mui/material";
import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/CustomRadioGroup"; import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/CustomRadioGroup";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
import { AllTypesQuestion } from "@/model/questionTypes/shared";
type ItemsSelectionViewProps = { type ItemsSelectionViewProps = {
type?: string; type?: string;

@ -6,6 +6,7 @@ import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
import { VKPixelLogo } from "../mocks/VKPixelLogo"; import { VKPixelLogo } from "../mocks/VKPixelLogo";
import { QuizMetricType } from "@model/quizSettings"; import { QuizMetricType } from "@model/quizSettings";
import { AmoCRMLogo } from "../mocks/AmoCRMLogo"; import { AmoCRMLogo } from "../mocks/AmoCRMLogo";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
const AnalyticsModal = lazy(() => const AnalyticsModal = lazy(() =>
import("./AnalyticsModal/AnalyticsModal").then((module) => ({ import("./AnalyticsModal/AnalyticsModal").then((module) => ({
@ -43,6 +44,8 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const quiz = useCurrentQuiz();
return ( return (
<Box <Box
sx={{ sx={{
@ -125,6 +128,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
isModalOpen={isAmoCrmModalOpen} isModalOpen={isAmoCrmModalOpen}
handleCloseModal={handleCloseAmoSRMModal} handleCloseModal={handleCloseAmoSRMModal}
companyName={companyName} companyName={companyName}
quizID={quiz?.backendId}
/> />
</Suspense> </Suspense>
)} )}

@ -4,13 +4,17 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import type { QuizQuestionDate } from "../../../model/questionTypes/date";
type SettingsDataProps = { type SettingsDataProps = {
question: QuizQuestionDate; questionId: string;
isRequired: boolean;
isDateRange: boolean;
isTime: boolean;
}; };
export default function SettingsData({ question }: SettingsDataProps) { export default function SettingsData({ questionId, isRequired, isDateRange, isTime }: SettingsDataProps) {
const theme = useTheme(); const theme = useTheme();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
return ( return (
@ -18,19 +22,66 @@ export default function SettingsData({ question }: SettingsDataProps) {
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isWrappColumn ? "column" : null, flexDirection: isTablet ? "column" : "row",
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box <Box
sx={{ sx={{
boxSizing: "border-box",
pt: "20px", pt: "20px",
pb: "20px",
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
pr: isFigmaTablte ? "19px" : "20px", pr: isFigmaTablte ? "19px" : "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: isMobile ? "13px" : "14px", gap: "14px",
width: isMobile ? "auto" : "100%", width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Настройки ответов
</Typography>
<CustomCheckbox
dataCy="checkbox-dateRange"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Выбор диапазона дат"}
checked={isDateRange}
handleChange={({ target }) => {
updateQuestion<QuizQuestionDate>(questionId, (question) => {
question.content.dateRange = target.checked;
});
}}
/>
<CustomCheckbox
dataCy="checkbox-time"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Выбор времени"}
checked={isTime}
handleChange={({ target }) => {
updateQuestion<QuizQuestionDate>(questionId, (question) => {
question.content.time = target.checked;
});
}}
/>
</Box>
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}} }}
> >
<Typography <Typography
@ -47,9 +98,9 @@ export default function SettingsData({ question }: SettingsDataProps) {
dataCy="checkbox-optional-question" dataCy="checkbox-optional-question"
sx={{ mr: isMobile ? "0px" : "16px" }} sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Необязательный вопрос"} label={"Необязательный вопрос"}
checked={!question.content.required} checked={!isRequired}
handleChange={({ target }) => { handleChange={({ target }) => {
updateQuestion<QuizQuestionDate>(question.id, (question) => { updateQuestion<QuizQuestionDate>(questionId, (question) => {
question.content.required = !target.checked; question.content.required = !target.checked;
}); });
}} }}

@ -7,13 +7,17 @@ interface Props {
question: QuizQuestionDate; question: QuizQuestionDate;
} }
export default function SwitchData({ export default function SwitchData({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return <SettingData question={question} />; return (
<SettingData
questionId={question.id}
isRequired={question.content.required}
isDateRange={question.content.dateRange}
isTime={question.content.time}
/>
);
case "help": case "help":
return ( return (
<HelpQuestions <HelpQuestions

@ -3,19 +3,20 @@ import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
import { memo } from "react"; import { memo } from "react";
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
type SettingEmojiProps = { type SettingEmojiProps = {
questionId: string; questionId: string;
isRequired: boolean; isRequired: boolean;
isMulti: boolean;
isOwn: boolean;
}; };
const SettingEmoji = memo<SettingEmojiProps>(function ({ const SettingEmoji = memo<SettingEmojiProps>(function ({ questionId, isRequired, isMulti, isOwn }) {
questionId,
isRequired,
}) {
const theme = useTheme(); const theme = useTheme();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isTablet = useMediaQuery(theme.breakpoints.down(985));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
return ( return (
@ -24,13 +25,58 @@ const SettingEmoji = memo<SettingEmojiProps>(function ({
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isWrappColumn ? "column" : "none", flexDirection: isWrappColumn ? "column" : "none",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Настройки ответов
</Typography>
<CustomCheckbox
dataCy="checkbox-multiple-answers"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Можно несколько"}
checked={isMulti}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.multi = target.checked;
});
}}
/>
<CustomCheckbox
dataCy="checkbox-own-answer"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={'Вариант "свой ответ"'}
checked={isOwn}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.own = target.checked;
});
}}
/>
</Box>
<Box <Box
sx={{ sx={{
pt: "20px", pt: "20px",
pb: "20px",
pl: "20px",
pr: isFigmaTablte ? "30px" : "20px", pr: isFigmaTablte ? "30px" : "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

@ -7,16 +7,15 @@ interface Props {
question: QuizQuestionEmoji; question: QuizQuestionEmoji;
} }
export default function SwitchEmoji({ export default function SwitchEmoji({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return ( return (
<SettingEmoji <SettingEmoji
questionId={question.id} questionId={question.id}
isRequired={question.content.required} isRequired={question.content.required}
isOwn={question.content.own}
isMulti={question.content.multi}
/> />
); );
case "help": case "help":

@ -4,21 +4,20 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
import { memo } from "react"; import { memo } from "react";
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
type SettingOptionsAndPictProps = { type SettingOptionsAndPictProps = {
questionId: string; questionId: string;
replText: string; replText: string;
isRequired: boolean; isRequired: boolean;
isOwn: boolean;
}; };
const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ questionId, replText, isRequired, isOwn }) {
questionId,
replText,
isRequired,
}) {
const theme = useTheme(); const theme = useTheme();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isTablet = useMediaQuery(theme.breakpoints.down(985));
const isMobile = useMediaQuery(theme.breakpoints.down(680)); const isMobile = useMediaQuery(theme.breakpoints.down(680));
const setReplText = (replText: string) => { const setReplText = (replText: string) => {
@ -36,13 +35,14 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isWrappColumn ? "column" : "none", flexDirection: isWrappColumn ? "column" : "none",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box <Box
sx={{ sx={{
pt: isMobile ? "30px" : "20px", pt: "20px",
pb: isMobile ? "25px" : "20px",
pl: "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: "14px", gap: "14px",
@ -60,6 +60,17 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({
> >
Настройки ответов Настройки ответов
</Typography> </Typography>
<CustomCheckbox
dataCy="checkbox-own-answer"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={'Вариант "свой ответ"'}
checked={isOwn}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.own = target.checked;
});
}}
/>
{!isWrappColumn && ( {!isWrappColumn && (
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}> <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
<Typography <Typography
@ -90,8 +101,6 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({
<Box <Box
sx={{ sx={{
pt: isMobile ? "0px" : "20px", pt: isMobile ? "0px" : "20px",
pb: "20px",
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "31px") : "20px",
pr: isFigmaTablte ? "19px" : "20px", pr: isFigmaTablte ? "19px" : "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
@ -123,7 +132,16 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({
} }
/> />
{isWrappColumn && ( {isWrappColumn && (
<> <Box
sx={{
pt: "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
maxWidth: isFigmaTablte ? "297px" : "360px",
width: "100%",
}}
>
<Typography <Typography
sx={{ sx={{
height: isMobile ? "18px" : "auto", height: isMobile ? "18px" : "auto",
@ -141,7 +159,7 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({
maxLength={60} maxLength={60}
onChange={({ target }) => setReplText(target.value)} onChange={({ target }) => setReplText(target.value)}
/> />
</> </Box>
)} )}
</Box> </Box>
</Box> </Box>

@ -8,10 +8,7 @@ interface Props {
question: QuizQuestionVarImg; question: QuizQuestionVarImg;
} }
export default function SwitchOptionsAndPict({ export default function SwitchOptionsAndPict({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return ( return (
@ -19,6 +16,7 @@ export default function SwitchOptionsAndPict({
questionId={question.id} questionId={question.id}
replText={question.content.replText} replText={question.content.replText}
isRequired={question.content.required} isRequired={question.content.required}
isOwn={question.content.own}
/> />
); );
case "help": case "help":
@ -30,12 +28,7 @@ export default function SwitchOptionsAndPict({
/> />
); );
case "image": case "image":
return ( return <UploadImage question={question} cropAspectRatio={{ width: 380, height: 300 }} />;
<UploadImage
question={question}
cropAspectRatio={{ width: 380, height: 300 }}
/>
);
default: default:
return null; return null;
} }

@ -1,10 +1,4 @@
import { import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
Box,
Button,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11"; import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
@ -12,6 +6,9 @@ import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIc
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21"; import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
import type { QuizQuestionImages } from "../../../model/questionTypes/images"; import type { QuizQuestionImages } from "../../../model/questionTypes/images";
import { memo } from "react"; import { memo } from "react";
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
type Proportion = "1:1" | "2:1" | "1:2"; type Proportion = "1:1" | "2:1" | "1:2";
@ -26,14 +23,34 @@ const PROPORTIONS: ProportionItem[] = [
{ value: "1:2", icon: ProportionsIcon12 }, { value: "1:2", icon: ProportionsIcon12 },
]; ];
type Format = "carousel" | "masonry";
type FormatItem = {
value: Format;
icon: (props: { color: string }) => JSX.Element;
};
const FORMATS: FormatItem[] = [
{ value: "masonry", icon: FormatIcon2 },
{ value: "carousel", icon: FormatIcon1 },
];
type SettingOpytionsPictProps = { type SettingOpytionsPictProps = {
questionId: string; questionId: string;
isRequired: boolean; isRequired: boolean;
isMulti: boolean;
isOwn: boolean;
proportions: Proportion;
format: Format;
}; };
const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
questionId, questionId,
isRequired, isRequired,
isMulti,
isOwn,
proportions,
format,
}) { }) {
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(985)); const isTablet = useMediaQuery(theme.breakpoints.down(985));
@ -47,38 +64,167 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isTablet ? "column" : null, flexDirection: isTablet ? "column" : null,
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px", marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
sx={{ <Box
pt: isMobile ? "25px" : "20px", sx={{
pb: isMobile ? "25px" : "20px", boxSizing: "border-box",
pl: "20px", pt: "20px",
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px", pr: isFigmaTablte ? "19px" : "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: "14px", gap: "14px",
width: "100%", width: "100%",
}} }}
>
<Typography
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
> >
Настройки вопросов <Typography
</Typography> sx={{
<CustomCheckbox height: isMobile ? "18px" : "auto",
dataCy="checkbox-optional-question" fontWeight: "500",
sx={{ alignItems: isMobile ? "flex-start" : "" }} fontSize: "18px",
label={"Необязательный вопрос"} color: " #4D4D4D",
checked={!isRequired} }}
handleChange={({ target }) => >
updateQuestion<QuizQuestionImages>(questionId, (question) => { Пропорции
if (question.type !== "images") return; </Typography>
<Box
sx={{
display: "flex",
gap: "10px",
}}
>
{PROPORTIONS.map((proportionItem, index) => (
<SelectIconButton
key={index}
Icon={proportionItem.icon}
isActive={proportionItem.value === proportions}
onClick={() => {
updateQuestion<QuizQuestionImages>(questionId, (question) => {
if (question.type !== "images") return;
question.content.xy = proportionItem.value;
});
}}
/>
))}
</Box>
</Box>
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Настройки ответов
</Typography>
<CustomCheckbox
dataCy="checkbox-multiple-answers"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Можно несколько"}
checked={isMulti}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.multi = target.checked;
});
}}
/>
<CustomCheckbox
dataCy="checkbox-own-answer"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={'Вариант "свой ответ"'}
checked={isOwn}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.own = target.checked;
});
}}
/>
</Box>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Формат
</Typography>
<Box
sx={{
display: "flex",
gap: "10px",
}}
>
{FORMATS.map((formatItem, index) => (
<SelectIconButton
key={index}
Icon={formatItem.icon}
isActive={formatItem.value === format}
onClick={() => {
updateQuestion<QuizQuestionImages>(questionId, (question) => {
if (question.type !== "images") return;
question.content.format = formatItem.value;
});
}}
/>
))}
</Box>
</Box>
<Box
sx={{
pt: "20px",
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}}
>
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>Настройки вопросов</Typography>
<CustomCheckbox
dataCy="checkbox-optional-question"
sx={{ alignItems: isMobile ? "flex-start" : "" }}
label={"Необязательный вопрос"}
checked={!isRequired}
handleChange={({ target }) =>
updateQuestion<QuizQuestionImages>(questionId, (question) => {
if (question.type !== "images") return;
question.content.required = !target.checked; question.content.required = !target.checked;
}) })
} }
/> />
</Box>
</Box> </Box>
</Box> </Box>
); );
@ -101,23 +247,13 @@ export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
<Button <Button
onClick={onClick} onClick={onClick}
variant="outlined" variant="outlined"
startIcon={ startIcon={<Icon color={isActive ? theme.palette.navbarbg.main : theme.palette.brightPurple.main} />}
<Icon
color={
isActive
? theme.palette.navbarbg.main
: theme.palette.brightPurple.main
}
/>
}
sx={{ sx={{
backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc", backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc",
borderRadius: 0, borderRadius: 0,
border: "none", border: "none",
color: isActive color: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
? theme.palette.brightPurple.main
: theme.palette.grey2.main,
p: "7px", p: "7px",
width: "40px", width: "40px",
height: "40px", height: "40px",
@ -128,9 +264,7 @@ export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
}, },
"&:hover": { "&:hover": {
border: "none", border: "none",
borderColor: isActive borderColor: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
? theme.palette.brightPurple.main
: theme.palette.grey2.main,
}, },
}} }}
/> />

@ -7,16 +7,17 @@ interface Props {
question: QuizQuestionImages; question: QuizQuestionImages;
} }
export default function SwitchAnswerOptionsPict({ export default function SwitchAnswerOptionsPict({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return ( return (
<SettingOpytionsPict <SettingOpytionsPict
questionId={question.id} questionId={question.id}
isRequired={question.content.required} isRequired={question.content.required}
isMulti={question.content.multi}
isOwn={question.content.own}
proportions={question.content.xy}
format={question.content.format}
/> />
); );
case "help": case "help":

@ -1,31 +1,36 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, FormControlLabel, Radio, RadioGroup, Typography, useMediaQuery, useTheme } from "@mui/material";
import { updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import type { QuizQuestionText } from "../../../model/questionTypes/text"; import type { QuizQuestionText } from "../../../model/questionTypes/text";
import { memo } from "react"; import { memo } from "react";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
type Answer = "single" | "multi" | "numberOnly";
type AnswerItem = {
name: string;
value: Answer;
};
const ANSWER_TYPES: AnswerItem[] = [
{ name: "Однострочное", value: "single" },
{ name: "Многострочное", value: "multi" },
{ name: "Только числа", value: "numberOnly" },
];
type SettingTextFieldProps = { type SettingTextFieldProps = {
questionId: string; questionId: string;
isRequired: boolean; isRequired: boolean;
isAutofill: boolean;
answerType: Answer;
}; };
type Answer = { const SettingTextField = memo<SettingTextFieldProps>(function ({ questionId, isRequired, isAutofill, answerType }) {
name: string;
value: "single" | "multi";
};
const ANSWER_TYPES: Answer[] = [
{ name: "Однострочное", value: "single" },
{ name: "Многострочное", value: "multi" },
];
const SettingTextField = memo<SettingTextFieldProps>(function ({
questionId,
isRequired,
}) {
const theme = useTheme(); const theme = useTheme();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
return ( return (
@ -33,19 +38,67 @@ const SettingTextField = memo<SettingTextFieldProps>(function ({
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isWrappColumn ? "column" : null, flexDirection: isTablet ? "column" : "row",
marginRight: isFigmaTablte ? "0px" : "32px", marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box <Box
sx={{ sx={{
boxSizing: "border-box",
pt: "20px", pt: "20px",
pb: "20px", pr: isFigmaTablte ? "19px" : "20px",
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
pr: "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: "14px", gap: "14px",
width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Настройки ответов
</Typography>
<RadioGroup
value={answerType}
onChange={(event) => {
updateQuestion<QuizQuestionText>(questionId, (question) => {
question.content.answerType = event.target.value as Answer;
});
}}
sx={{
display: "flex",
flexDirection: "column",
}}
>
{ANSWER_TYPES.map((answerTypeItem, index) => (
<FormControlLabel
value={answerTypeItem.value}
control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
label={answerTypeItem.name}
sx={{
color: theme.palette.grey2.main,
}}
/>
))}
</RadioGroup>
</Box>
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px",
display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}} }}
> >
<Typography <Typography
@ -58,6 +111,21 @@ const SettingTextField = memo<SettingTextFieldProps>(function ({
> >
Настройки вопросов Настройки вопросов
</Typography> </Typography>
<CustomCheckbox
dataCy="checkbox-optional-autofill"
sx={{
display: isMobile ? "flex" : "block",
mr: isMobile ? "0px" : "16px",
alignItems: isMobile ? "flex-end" : "center",
}}
label={"Автозаполнение адреса"}
checked={isAutofill}
handleChange={(e) => {
updateQuestion<QuizQuestionText>(questionId, (question) => {
question.content.autofill = e.target.checked;
});
}}
/>
<CustomCheckbox <CustomCheckbox
dataCy="checkbox-optional-question" dataCy="checkbox-optional-question"
sx={{ sx={{

@ -8,25 +8,20 @@ interface Props {
question: QuizQuestionText; question: QuizQuestionText;
} }
export default function SwitchTextField({ export default function SwitchTextField({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return ( return (
<SettingTextField <SettingTextField
questionId={question.id} questionId={question.id}
isRequired={question.content.required} isRequired={question.content.required}
isAutofill={question.content.autofill}
isOnlyNumbers={question.content.onlyNumbers}
answerType={question.content.answerType}
/> />
); );
case "image": case "image":
return ( return <UploadImage question={question} cropAspectRatio={{ width: 400, height: 300 }} />;
<UploadImage
question={question}
cropAspectRatio={{ width: 400, height: 300 }}
/>
);
case "help": case "help":
return ( return (
<HelpQuestions <HelpQuestions

@ -7,9 +7,12 @@ import { memo } from "react";
interface Props { interface Props {
questionId: string; questionId: string;
isRequired: boolean; isRequired: boolean;
isLargeCheck: boolean;
isMulti: boolean;
isOwn: boolean;
} }
const ResponseSettings = memo<Props>(function ({ questionId, isRequired }) { const ResponseSettings = memo<Props>(function ({ questionId, isRequired, isLargeCheck, isMulti, isOwn }) {
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(900)); const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
@ -20,16 +23,72 @@ const ResponseSettings = memo<Props>(function ({ questionId, isRequired }) {
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isTablet ? "column" : "none", flexDirection: isTablet ? "column" : "row",
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px", marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
pb: "20px",
pl: "20px",
pt: isTablet ? "5px" : "0px",
}} }}
> >
<Box <Box
sx={{ sx={{
boxSizing: "border-box", boxSizing: "border-box",
pt: "20px", pt: "20px",
pb: "20px", pr: isFigmaTablte ? "19px" : "20px",
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px", display: "flex",
flexDirection: "column",
gap: "14px",
width: "100%",
}}
>
<Typography
sx={{
height: isMobile ? "18px" : "auto",
fontWeight: "500",
fontSize: "18px",
color: " #4D4D4D",
}}
>
Настройки ответов
</Typography>
<CustomCheckbox
dataCy="checkbox-long-text-answer"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Длинный текстовый ответ"}
checked={isLargeCheck}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.largeCheck = target.checked;
});
}}
/>
<CustomCheckbox
dataCy="checkbox-multiple-answers"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={"Можно несколько"}
checked={isMulti}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.multi = target.checked;
});
}}
/>
<CustomCheckbox
dataCy="checkbox-own-answer"
sx={{ mr: isMobile ? "0px" : "16px" }}
label={'Вариант "свой ответ"'}
checked={isOwn}
handleChange={({ target }) => {
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
question.content.own = target.checked;
});
}}
/>
</Box>
<Box
sx={{
boxSizing: "border-box",
pt: "20px",
pr: isFigmaTablte ? "19px" : "20px", pr: isFigmaTablte ? "19px" : "20px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

@ -8,16 +8,16 @@ interface Props {
question: QuizQuestionVariant; question: QuizQuestionVariant;
} }
export default function SwitchAnswerOptions({ export default function SwitchAnswerOptions({ switchState = "setting", question }: Props) {
switchState = "setting",
question,
}: Props) {
switch (switchState) { switch (switchState) {
case "setting": case "setting":
return ( return (
<ResponseSettings <ResponseSettings
questionId={question.id} questionId={question.id}
isRequired={question.content.required} isRequired={question.content.required}
isLargeCheck={question.content.largeCheck}
isMulti={question.content.multi}
isOwn={question.content.own}
/> />
); );
case "help": case "help":
@ -29,12 +29,7 @@ export default function SwitchAnswerOptions({
/> />
); );
case "image": case "image":
return ( return <UploadImage question={question} cropAspectRatio={{ width: 380, height: 300 }} />;
<UploadImage
question={question}
cropAspectRatio={{ width: 380, height: 300 }}
/>
);
default: default:
return null; return null;
} }

@ -1,5 +1,6 @@
import { ButtonBase } from "@mui/material"; import { ButtonBase, Popover } from "@mui/material";
import { startTransition, useRef } from "react"; import { startTransition, useState } from "react";
import { HexAlphaColorPicker } from "react-colorful";
interface Props { interface Props {
color: string; color: string;
@ -7,35 +8,43 @@ interface Props {
} }
export default function CircleColorPicker({ color, onChange }: Props) { export default function CircleColorPicker({ color, onChange }: Props) {
const inputRef = useRef<HTMLInputElement>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
return ( return (
<ButtonBase <>
onClick={() => inputRef.current?.click()} <ButtonBase
sx={{ onClick={(event) => setAnchorEl(event.currentTarget)}
aspectRatio: 1, sx={{
height: "22px", aspectRatio: 1,
width: "22px", height: "22px",
minWidth: "22px", width: "22px",
borderRadius: "50%", minWidth: "22px",
backgroundColor: color, borderRadius: "50%",
border: "1px solid #4D4D4D", backgroundColor: color,
}} border: "1px solid #4D4D4D",
>
<input
ref={inputRef}
type="color"
value={color}
onChange={(e) => {
startTransition(() => {
onChange(e.target.value);
});
}}
style={{
opacity: 0,
cursor: "pointer",
}} }}
/> />
</ButtonBase> <Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
slotProps={{
paper: {
sx: {
p: "20px",
},
style: {
backgroundColor: color,
},
},
}}
>
<HexAlphaColorPicker color={color} onChange={(color) => startTransition(() => onChange(color))} />
</Popover>
</>
); );
} }

@ -25,7 +25,7 @@ const translateMessage: Record<string, string> = {
export const parseAxiosError = (nativeError: unknown): [string, number?] => { export const parseAxiosError = (nativeError: unknown): [string, number?] => {
const error = nativeError as AxiosError; const error = nativeError as AxiosError;
console.log(error)
if (process.env.NODE_ENV !== "production") console.error(error); if (process.env.NODE_ENV !== "production") console.error(error);
if (error.message === "Failed to fetch") return ["Ошибка сети"]; if (error.message === "Failed to fetch") return ["Ошибка сети"];

@ -9486,6 +9486,11 @@ react-beautiful-dnd@^13.1.1:
redux "^4.0.4" redux "^4.0.4"
use-memo-one "^1.1.1" use-memo-one "^1.1.1"
react-colorful@^5.6.1:
version "5.6.1"
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
react-cytoscapejs@^2.0.0: react-cytoscapejs@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/react-cytoscapejs/-/react-cytoscapejs-2.0.0.tgz#fdc2547626df0678acfbb48e73a437ddc1687b01" resolved "https://registry.yarnpkg.com/react-cytoscapejs/-/react-cytoscapejs-2.0.0.tgz#fdc2547626df0678acfbb48e73a437ddc1687b01"